local prefabs =
{
    "shadow_despawn",
    "shadow_glob_fx",
    "statue_transition_2",
    "nightmarefuel",
    "ocean_splash_med1",
    "ocean_splash_med2",
    "ocean_splash_small1",
    "ocean_splash_small2",
    "shadowstrike_slash_fx",
    "shadowstrike_slash2_fx",
}

local brain = require("brains/bboy_paper_effigybrain")

SetSharedLootTable('bboy_paper_effigy',
    {
        { 'papyrus',       1.00 },
        { 'papyrus',       1.00 },
        { 'nightmarefuel', 0.33 },
    })

local function SaveSpawnPoint(inst, dont_overwrite)
    if not dont_overwrite or
        (inst.components.knownlocations:GetLocation("spawn") == nil and
            inst.components.knownlocations:GetLocation("spawnplatform") == nil
        ) then
        local x, y, z = inst.Transform:GetWorldPosition()
        local platform = TheWorld.Map:GetPlatformAtPoint(x, z)
        if platform ~= nil then
            x, y, z = platform.entity:WorldToLocalSpace(x, 0, z)
            inst.components.knownlocations:ForgetLocation("spawn")
            inst.components.knownlocations:RememberLocation("spawnplatform", Vector3(x, 0, z))
            inst.components.entitytracker:TrackEntity("spawnplatform", platform)
        else
            inst.components.entitytracker:ForgetEntity("spawnplatform")
            inst.components.knownlocations:ForgetLocation("spawnplatform")
            inst.components.knownlocations:RememberLocation("spawn", Vector3(x, 0, z))
        end
    end
end

local function GetSpawnPoint(inst)
    local pt = inst.components.knownlocations:GetLocation("spawn")
    if pt ~= nil then
        return pt
    end
    pt = inst.components.knownlocations:GetLocation("spawnplatform")
    if pt ~= nil then
        local platform = inst.components.entitytracker:GetEntity("spawnplatform")
        if platform ~= nil then
            local x, y, z = platform.entity:LocalToWorldSpace(pt:Get())
            return Vector3(x, 0, z)
        end
    end
end

local function MakeSpawnPointTracker(inst)
    inst:AddComponent("knownlocations")
    inst:AddComponent("entitytracker")
    inst.SaveSpawnPoint = SaveSpawnPoint -- 保存生成点
    inst.GetSpawnPoint = GetSpawnPoint   -- 获取生成点
end

local function OnAttacked(inst, data)
    if data.attacker ~= nil then
        -- 如果攻击者是自己的主人,则立马消失
        if data.attacker.components.petleash ~= nil and
            data.attacker.components.petleash:IsPet(inst) then
            data.attacker.components.petleash:DespawnPet(inst)
        elseif data.attacker.components.combat ~= nil then
            inst.components.combat:SuggestTarget(data.attacker)
        end
    end
end

local function DoRemove(inst)
    if inst.components.inventory ~= nil then
        inst.components.inventory:DropEverything(true)
    end
    inst:Remove()
end

local function OnSeekOblivion(inst)
    -- 如果出了加载范围就丢下身上所有的物品并移除
    if inst:IsAsleep() then
        DoRemove(inst)
        return
    end
    -- obliviate 定时器停止
    inst.components.timer:StopTimer("obliviate")
    if inst.components.health == nil then
        inst.sg:GoToState("quickdespawn")
    elseif inst.components.health:IsInvincible() then
        -- 如果当前帧是无敌的,重新开始
        inst.components.timer:StartTimer("obliviate", .5)
    else
        -- 杀死,主人离开了,结果都是消失
        inst:StopBrain()
        inst:SetBrain(nil)
        inst.components.health:Kill()
    end
end

local function OnTimerDone(inst, data)
    if data and data.name == "obliviate" then
        OnSeekOblivion(inst)
    end
end

local function OnEntitySleep(inst)
    if inst._obliviatetask == nil then
        inst._obliviatetask = inst:DoTaskInTime(TUNING.SHADOWWAXWELL_MINION_IDLE_DESPAWN_TIME, DoRemove)
    end
end

local function OnEntityWake(inst)
    if inst._obliviatetask ~= nil then
        inst._obliviatetask:Cancel()
        inst._obliviatetask = nil
    end
end

local function MakeOblivionSeeker(inst, duration)
    inst:ListenForEvent("timerdone", OnTimerDone)
    inst:AddComponent("timer")
    inst.components.timer:StartTimer("obliviate", duration)
    -- inst.OnEntitySleep = OnEntitySleep
    -- inst.OnEntityWake = OnEntityWake
end

local function DropAggro(inst)
    local leader = inst.components.follower:GetLeader()
    if leader ~= nil and
        ((leader.components.health ~= nil and leader.components.health:IsDead()) or
            (leader.sg ~= nil and leader.sg:HasStateTag("hiding")) or
            not inst:IsNear(leader, TUNING.SHADOWWAXWELL_PROTECTOR_TRANSFER_AGGRO_RANGE) or
            not leader.entity:IsVisible() or
            leader:HasTag("playerghost")
        ) then
        -- 死亡,隐藏,或超出范围
        leader = nil
    end
    -- 没有主人则直接丢失仇恨
    inst:PushEvent("transfercombattarget", leader)
end

local function OnDancingPlayerData(inst, data)
    if data == nil then
        return
    end

    local player = data.inst
    if player == nil or player ~= inst.components.follower:GetLeader() then
        return
    end

    inst._brain_dancedata = data.dancedata
end

local COMBAT_MUSHAVE_TAGS = { "_combat", "_health" }
local COMBAT_CANTHAVE_TAGS = { "INLIMBO", "companion" }
local COMBAT_MUSTONEOF_TAGS_AGGRESSIVE = { "monster", "prey", "insect", "hostile", "character", "animal" }
local function HasFriendlyLeader(inst, target)
    local leader = inst.components.follower.leader
    if leader ~= nil then
        local target_leader = (target.components.follower ~= nil) and target.components.follower.leader or nil

        if target_leader and target_leader.components.inventoryitem then
            target_leader = target_leader.components.inventoryitem:GetGrandOwner()
            -- Don't attack followers if their follow object has no owner
            if target_leader == nil then
                return true
            end
        end

        local PVP_enabled = TheNet:GetPVPEnabled()

        return leader == target or (target_leader ~= nil
                and (target_leader == leader or (target_leader:HasTag("player")
                    and not PVP_enabled))) or
            (target.components.domesticatable and target.components.domesticatable:IsDomesticated()
                and not PVP_enabled) or
            (target.components.saltlicker and target.components.saltlicker.salted
                and not PVP_enabled)
    end

    return false
end
local function protectorretargetfn(inst)
    -- 在跳舞时不重新选择目标
    if inst.sg:HasStateTag("dancing") then
        return nil
    end

    -- 没有生成点不重新选择目标
    -- local spawn = inst:GetSpawnPoint()
    -- if spawn == nil then
    --     return nil
    -- end
    local x, y, z = inst.Transform:GetWorldPosition()

    local target = nil
    local ents = TheSim:FindEntities(x, y, z, TUNING.SHADOWWAXWELL_PROTECTOR_DEFEND_RADIUS, COMBAT_MUSHAVE_TAGS, COMBAT_CANTHAVE_TAGS, COMBAT_MUSTONEOF_TAGS_AGGRESSIVE)
    for _, ent in ipairs(ents) do
        if ent ~= inst and ent.entity:IsVisible()
            and inst.components.combat:CanTarget(ent)
            and ent.components.minigame_participator == nil
            and not HasFriendlyLeader(inst, ent) then
            target = ent
            break
        end
    end

    return target
end
local function protectorkeeptargetfn(inst, target)
    -- 如果可以的话,维持目标。
    return inst.components.combat:CanTarget(target)
        and not inst.sg:HasStateTag("dancing")
        and target.components.minigame_participator == nil
        and (not target:HasTag("player") or TheNet:GetPVPEnabled())
end
-- 最大承受伤害减少1点
local function protector_updatehealthclamp(inst)
    local cap = math.abs(inst.components.health.maxdamagetakenperhit)
    cap = cap + math.abs(5)
    cap = math.min(cap, math.max(1, inst.components.health.maxhealth - 1))
    inst.components.health:SetMaxDamageTakenPerHit(cap)
end
-- 有了新的仇恨目标之后,战斗过程中最大承受伤害每2.5秒降低一点
local function protector_onengaged(inst)
    if inst.disengagetask ~= nil then
        inst.disengagetask:Cancel()
        inst.disengagetask = nil
    end
    if inst.engagedtask == nil then
        inst.engagedtask = inst:DoPeriodicTask(2.5, protector_updatehealthclamp, 5)
    end
end
local function protector_resethealthclamp(inst)
    inst.disengagetask = nil
    if inst.engagedtask ~= nil then
        inst.engagedtask:Cancel()
        inst.engagedtask = nil
    end
    inst.components.health:SetMaxDamageTakenPerHit(15)
end
-- 丢失仇恨之后,5秒之后移除身上全部定时器
local function protector_ondisengaged(inst)
    if inst.disengagetask == nil then
        inst.disengagetask = inst:DoTaskInTime(5, protector_resethealthclamp)
    end
end
-- 受到攻击之后,重置最大承受伤害,并重新开始 engagedtask 定时减少最大承受伤害任务
local function protector_attacked(inst, data)
    if data ~= nil and data.damage ~= nil and data.damage > 0 then
        inst.components.health:SetMaxDamageTakenPerHit(15)
        if inst.engagedtask ~= nil then
            inst.engagedtask:Cancel()
            inst.engagedtask = inst:DoPeriodicTask(2.5, protector_updatehealthclamp, 5) -- 初始延迟是5秒,之后是2.5秒
        end
    end
end

local function nodebrisdmg(inst, amount, overtime, cause, ignore_invincible, afflicter, ignore_absorb)
    return afflicter ~= nil and afflicter:HasTag("quakedebris")
end

--------------------------------------------------------------------------

local function OnRippleAnimOver(inst)
    if inst.pool.invalid then
        inst:Remove()
    else
        inst:Hide()
        table.insert(inst.pool, inst)
    end
end

local function CreateRipple(pool)
    local inst
    if #pool > 0 then
        inst = table.remove(pool)
        inst:Show()
    else
        inst = CreateEntity()

        inst:AddTag("FX")
        inst:AddTag("NOCLICK")
        -- [[Non-networked entity]]
        inst.entity:SetCanSleep(false)
        inst.persists = false

        inst.entity:AddTransform()
        inst.entity:AddAnimState()

        inst.AnimState:SetBank("splash_weregoose_fx")
        inst.AnimState:SetBuild("splash_water_drop")
        inst.AnimState:SetLayer(LAYER_WORLD_BACKGROUND)
        inst.AnimState:SetOceanBlendParams(TUNING.OCEAN_SHADER.EFFECT_TINT_AMOUNT)

        inst.pool = pool
        inst:ListenForEvent("animover", OnRippleAnimOver)
    end

    inst.AnimState:PlayAnimation(math.random() < .5 and "no_splash" or "no_splash2")
    local scale = .6 + math.random() * .2
    inst.AnimState:SetScale(math.random() < .5 and -scale or scale, scale)

    return inst
end

local function TryRipple(inst, map)
    if not (inst:HasTag("moving") or
            inst.AnimState:IsCurrentAnimation("appear") or
            inst.AnimState:IsCurrentAnimation("disappear") or
            inst.AnimState:IsCurrentAnimation("lunge_pst")
        ) then
        local x, y, z = inst.Transform:GetWorldPosition()
        if map:IsOceanAtPoint(x, 0, z) then
            CreateRipple(inst.ripple_pool).Transform:SetPosition(x, 0, z)
        end
    end
end

local function OnRemoveEntity(inst)
    for i, v in ipairs(inst.ripple_pool) do
        v:Remove()
    end
    inst.ripple_pool.invalid = true
end

--------------------------------------------------------------------------

local function MakeMinion(prefab, tool)
    local assets =
    {
        Asset("SOUND", "sound/maxwell.fsb"),

        Asset("ANIM", "anim/bboy_paper_effigy.zip"),

        Asset("ANIM", "anim/waxwell_minion_spawn.zip"),
        Asset("ANIM", "anim/waxwell_minion_appear.zip"),
        Asset("ANIM", "anim/splash_weregoose_fx.zip"),
        Asset("ANIM", "anim/splash_water_drop.zip"),

        Asset("ANIM", "anim/lavaarena_shadow_lunge.zip"),
        Asset("ANIM", "anim/waxwell_minion_idle.zip")
    }

    for _, toolname in ipairs(tool) do
        table.insert(assets, Asset("ANIM", "anim/" .. toolname .. ".zip"))
    end


    local function fn()
        local inst = CreateEntity()

        inst.entity:AddTransform()
        inst.entity:AddAnimState()
        inst.entity:AddSoundEmitter()
        inst.entity:AddNetwork()

        inst:SetPhysicsRadiusOverride(.5)
        MakeGhostPhysics(inst, 1, inst.physicsradiusoverride)

        inst.Transform:SetFourFaced(inst)

        inst.AnimState:SetBank("wilson")
        inst.AnimState:SetBuild("bboy_paper_effigy")
        inst.AnimState:OverrideSymbol("fx_wipe", "wilson_fx", "fx_wipe") -- 一个未知的Symbol
        inst.AnimState:PlayAnimation("minion_spawn")                     -- waxwell_minion_spawn 中的动画
        -- inst.AnimState:SetMultColour(0, 0, 0, .5)                        -- 改色
        inst.AnimState:UsePointFiltering(true)                           -- 不知道用途,直译的意思是"使用点过滤"

        inst.AnimState:AddOverrideBuild("waxwell_minion_spawn")
        inst.AnimState:AddOverrideBuild("waxwell_minion_appear")
        inst.AnimState:AddOverrideBuild("lavaarena_shadow_lunge")
        inst.AnimState:Hide("ARM_carry")

        inst.AnimState:Hide("HAT")
        inst.AnimState:Hide("HAIR_HAT")

        inst:AddTag("scarytoprey")  -- 可怕的猎物
        inst:AddTag("shadowminion") -- 暗影仆从
        inst:AddTag("NOBLOCK")      -- 无法锁定

        -- inst:SetPrefabNameOverride("shadowwaxwell") -- 不需要

        -- 专用服务器不需要生成本地特效
        if not TheNet:IsDedicated() then
            inst.ripple_pool = {}
            inst:DoPeriodicTask(.6, TryRipple, math.random() * .6, TheWorld.Map)
            inst.OnRemoveEntity = OnRemoveEntity
        end

        inst.entity:SetPristine()

        if not TheWorld.ismastersim then
            return inst
        end

        -- 皮肤相关,用不到
        -- inst:AddComponent("skinner")
        -- inst.components.skinner:SetupNonPlayerData()

        inst:AddComponent("locomotor")
        inst.components.locomotor.runspeed = TUNING.SHADOWWAXWELL_SPEED
        inst.components.locomotor:SetTriggersCreep(false) -- 不会被蛛网,地陷等减速,我猜的
        inst.components.locomotor.pathcaps = { ignorecreep = true }
        inst.components.locomotor:SetSlowMultiplier(.6)   -- 如果减速就是原速度的0.6倍
        inst.components.locomotor.runspeed = TUNING.SHADOWWAXWELL_PROTECTOR_SPEED

        inst:AddComponent("health")
        -- inst.components.health:SetMaxHealth(1)
        inst.components.health:SetMaxHealth(100)
        inst.components.health:SetMaxDamageTakenPerHit(15) -- 每次命中最大伤害
        inst.components.health.nofadeout = true            -- 不懂
        inst.components.health.redirect = nodebrisdmg      -- 地震不会受伤害
        inst.components.health.fire_damage_scale = 5       -- 火焰伤害倍率

        inst:AddComponent("combat")
        inst.components.combat.hiteffectsymbol = "torso"
        inst.components.combat:SetRange(2)
        inst.components.combat:SetDefaultDamage(TUNING.SHADOWWAXWELL_PROTECTOR_DAMAGE) -- 20点默认伤害
        inst.components.combat:SetAttackPeriod(0.5)                                    -- 0.5秒攻击周期
        -- inst.components.combat:SetRetargetFunction(1, protectorretargetfn)
        -- inst.components.combat:SetKeepTargetFunction(protectorkeeptargetfn)

        -- 维持官方默认
        inst:AddComponent("follower")
        inst.components.follower:KeepLeaderOnAttacked()
        inst.components.follower.keepdeadleader = true
        inst.components.follower.keepleaderduringminigame = true
        -- inst.components.follower.noleashing = true

        inst:AddComponent("lootdropper")
        inst.components.lootdropper:SetChanceLootTable('bboy_paper_effigy')

        inst:SetBrain(brain)
        inst:SetStateGraph("SGbboy_paper_effigy")

        inst:ListenForEvent("attacked", OnAttacked)
        inst:ListenForEvent("seekoblivion", OnSeekOblivion) -- 当主人走出了仆从的行动范围
        inst:ListenForEvent("death", DropAggro)
        -- 跟着玩家一起跳舞
        inst:ListenForEvent("dancingplayerdata", function(world, data) OnDancingPlayerData(inst, data) end, TheWorld)

        inst.DropAggro = DropAggro
        -- 因为涉及到SG所以注释处理,官方代码的意思是:是否是战斗小人
        -- inst.isprotector = "shadowprotector"

        -- workerfn
        inst:AddComponent("inventory")
        inst.components.inventory.maxslots = 1

        -- protectorfn
        -- inst:ListenForEvent("newcombattarget", protector_onengaged)  -- 新的攻击目标
        -- inst:ListenForEvent("droppedtarget", protector_ondisengaged) -- 丢失仇恨
        -- inst:ListenForEvent("attacked", protector_attacked)

        -- MakeSpawnPointTracker(inst)
        MakeOblivionSeeker(inst, 360 + math.random())

        MakeMediumBurnableCharacter(inst, "torso")

        return inst
    end

    return Prefab(prefab, fn, assets, prefabs)
end

--------------------------------------------------------------------------

return MakeMinion("bboy_paper_effigy", { "swap_axe", "swap_pickaxe", "swap_shovel", "swap_nightmaresword_shadow" })
